using System.Text;

namespace SharpGraphT.Graph;

public abstract class AbstractGraph<TV, TE> : IGraph<TV, TE>
    where TE : class, new() {
    protected AbstractGraph() { }

    public bool ContainsEdge(TV sourceVertex, TV targetVertex) =>
        GetEdge(sourceVertex, targetVertex) != null;

    public virtual bool RemoveAllEdges<TEe>(IEnumerable<TEe> edges)
        where TEe : TE =>
        edges.Aggregate(false, (current, e) => current | RemoveEdge(e));

    public virtual IReadOnlySet<TE> RemoveAllEdges(TV sourceVertex,
        TV targetVertex) {
        var removed = GetAllEdges(sourceVertex, targetVertex);
        if (removed == null) {
            return null;
        }

        RemoveAllEdges(removed);

        return removed;
    }

    public virtual bool RemoveAllVertices<TVv>(ICollection<TVv> vertices)
        where TVv : TV =>
        vertices.Aggregate(false, (current, v) => current | RemoveVertex(v));

    public override string ToString() =>
        ToStringFromSets(VertexSet, EdgeSet, Type.IsDirected);

    protected bool AssertVertexExist(TV v) {
        if (ContainsVertex(v)) {
            return true;
        }

        if (v == null) {
            throw new NullReferenceException();
        }

        throw new ArgumentException($"no such vertex in graph: {v}");
    }

    protected bool RemoveAddEdges(TE[] edges) =>
        edges.Aggregate(false, (current, edge) => current | RemoveEdge(edge));

    protected string ToStringFromSets<TVv, TEe>(IEnumerable<TVv> vertexSet,
        IEnumerable<TEe> edgeSet, bool directed) where TVv : TV where TEe : TE {
        var renderedEdges = new List<string>();

        var sb = new StringBuilder();
        foreach (var e in edgeSet) {
            if (e is not DefaultEdge and not DefaultWeightedEdge) {
                sb.Append(e);
                sb.Append("=");
            }

            sb.Append(directed ? "(" : "{");

            sb.Append(GetEdgeSource(e));
            sb.Append(",");
            sb.Append(GetEdgeTarget(e));

            sb.Append(directed ? ")" : "}");

            renderedEdges.Add(sb.ToString());
            sb.Clear();
        }

        return $"({vertexSet}, {renderedEdges})";
    }

    // 这里没有按照JGraphT的hashCode实现GetHashCode。
    // 等未来单元测试遇到问题时再考虑解决方案。
    public override int GetHashCode() => base.GetHashCode();

    // 这里没有按照JGraphT的equals实现Equals。
    // 参考GraphEquals。
    public override bool Equals(object obj) => base.Equals(obj);

    public bool GraphEquals(IGraph<TV, TE> graph) {
        if (!VertexSet.SetEquals(graph.VertexSet)) {
            return false;
        }

        if (EdgeSet.Count != graph.EdgeSet.Count) {
            return false;
        }

        var isDirected = Type.IsDirected;
        foreach (var e in EdgeSet) {
            var source = GetEdgeSource(e);
            var target = GetEdgeTarget(e);

            if (!graph.ContainsEdge(e)) {
                return false;
            }

            var gSource = graph.GetEdgeSource(e);
            var gTarget = graph.GetEdgeTarget(e);

            if (isDirected) {
                if (!gSource.Equals(source) || !gTarget.Equals(target)) {
                    return false;
                }
            } else {
                if ((!gSource.Equals(source) || !gTarget.Equals(target)) &&
                    (!gSource.Equals(target) || !gTarget.Equals(source))) {
                    return false;
                }
            }

            if (GetEdgeWeight(e).CompareTo(graph.GetEdgeWeight(e)) != 0) {
                return false;
            }
        }

        return true;
    }

    public abstract IReadOnlySet<TE> GetAllEdges(TV sourceVertex,
        TV targetVertex);

    public abstract TE GetEdge(TV sourceVertex, TV targetVertex);
    public abstract Func<TV> VertexSupplier { get; }
    public abstract Func<TE> EdgeSupplier { get; }
    public abstract TE AddEdge(TV sourceVertex, TV targetVertex);
    public abstract bool AddEdge(TV sourceVertex, TV targetVertex, TE e);
    public abstract TV AddVertex();
    public abstract bool AddVertex(TV v);
    public abstract bool ContainsEdge(TE e);
    public abstract bool ContainsVertex(TV v);
    public abstract IReadOnlySet<TE> EdgeSet { get; }
    public abstract int DegreeOf(TV vertex);
    public abstract IReadOnlySet<TE> EdgesOf(TV vertex);
    public abstract int InDegreeOf(TV vertex);
    public abstract IReadOnlySet<TE> IncomingEdgesOf(TV vertex);
    public abstract int OutDegreeOf(TV vertex);
    public abstract IReadOnlySet<TE> OutgoingEdgesOf(TV vertex);
    public abstract TE RemoveEdge(TV sourceVertex, TV targetVertex);
    public abstract bool RemoveEdge(TE e);
    public abstract bool RemoveVertex(TV v);
    public abstract IReadOnlySet<TV> VertexSet { get; }
    public abstract TV GetEdgeSource(TE e);
    public abstract TV GetEdgeTarget(TE e);
    public abstract IGraphType Type { get; }
    public abstract double GetEdgeWeight(TE e);
    public abstract void SetEdgeWeight(TE e, double weight);
}